package com.hmdp.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.hmdp.utils.CacheClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.CACHE_SHOP_TTL;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Resource
	private CacheClient cacheClient;

	/**
	 * 查询商铺详情
	 * @param id 商店ID
	 * @return 商店信息
	 */
	@Override
	public Object queryById(Long id) {

		// 缓存穿透
//		Shop shop = cacheClient
//				.queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

		// 互斥锁解决缓存击穿
//		 Shop shop = queryWithMutex(id);

		// 逻辑过期解决缓存击穿
		Shop shop = cacheClient
				.queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

		if (shop == null) {
			return Result.fail("商铺不存在");
		}
		// 返回
		return Result.ok(shop);
	}

	// 缓存穿透
	/*public Shop queryWithPassThrough(Long id){
		String shopKey = CACHE_SHOP_KEY + id;

		// 从redis查询商店缓存
		String shopJson = stringRedisTemplate.opsForValue().get(shopKey);

		// 判断缓存是否存在
		if(StrUtil.isNotBlank(shopJson)){
			// 缓存存在，直接返回
			return JSONUtil.toBean(shopJson, Shop.class);
		}

		// 判断命中的是否是空值
		if(shopJson != null){
			// 命中空值，直接返回
			return null;
		}

		// 缓存不存在，查询数据库
		Shop shop = getById(id);

		// 店铺不存在, 返回错误
		if(shop == null){
			// 将空值写入redis
			stringRedisTemplate.opsForValue().set(shopKey, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
			// 返回错误信息
			return null;
		}

		// 店铺存在, 写入redus
		stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);

		// 返回
		return shop;
	}*/

	// 互斥锁解决缓存击穿
	/*public Shop queryWithMutex(Long id){
		String shopKey = CACHE_SHOP_KEY + id;

		// 从redis查询商店缓存
		String shopJson = stringRedisTemplate.opsForValue().get(shopKey);

		// 判断缓存是否存在
		if(StrUtil.isNotBlank(shopJson)){
			// 缓存存在，直接返回
			return JSONUtil.toBean(shopJson, Shop.class);
		}

		// 判断命中的是否是空值
		if(shopJson != null){
			// 命中空值，直接返回
			return null;
		}

		// 实现缓存重建
		// 1. 获取互斥锁
		String lockKey = "lock:shop:" + id;
		Shop shop = null;
		try{
			boolean isLock = tryLock(lockKey);
			// 2. 判断是否获取成功
			if(!isLock){
				// 3. 失败 则休眠并重试
				Thread.sleep(50);
				return queryWithMutex(id);
			}
			// 4. 成功 根据id查询数据库
			shop = getById(id);
			// 模拟重建延时
			Thread.sleep(1000);

			// 店铺不存在, 返回错误
			if(shop == null){
				// 将空值写入redis
				stringRedisTemplate.opsForValue().set(shopKey, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
				// 返回错误信息
				return null;
			}

			// 店铺存在, 写入redus
			stringRedisTemplate.opsForValue().set(shopKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);
		}catch(InterruptedException e){
			throw new RuntimeException(e);
		}finally{
			// 释放互斥锁
			unLock(lockKey);
		}

		// 返回
		return shop;
	}*/

	// 独立线程重建缓存(线程池)
	/*private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

	// 逻辑过期解决缓存击穿
	public Shop queryWithLogicalExpire(Long id){
		String shopKey = CACHE_SHOP_KEY + id;
		// 1. 从redis查询商店缓存
		String shopJson = stringRedisTemplate.opsForValue().get(shopKey);
		// 2. 判断缓存是否存在
		if(StrUtil.isBlank(shopJson)){
			// 3. 缓存存在，直接返回
			return null;
		}

		// 4. 命中, 需要先把json反序列化为对象
		RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
		Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
		LocalDateTime expireTime = redisData.getExpireTime();
		// 5. 判断是否过期
		if(expireTime.isAfter(LocalDateTime.now())){
			// 5.1 未过期, 直接返回
			return shop;
		}
		// 5.2 过期, 重建缓存
		// 6. 缓存重建
		// 6.1 获取互斥锁
		String lockKey = LOCK_SHOP_KEY + id;
		boolean isLock = tryLock(lockKey);
		// 6.2 判断是否获取成功
		if(isLock){
			// 6.3 成功, 开启独立线程, 实现缓存重建
			CACHE_REBUILD_EXECUTOR.submit(() -> {
				try {
					// 重建缓存
					this.saveShopRedis(id, 20L);
				} catch (Exception e) {
					throw new RuntimeException(e);
				} finally {
					// 释放互斥锁
					unLock(lockKey);
				}
			});
		}
		// 6.4 返回过期的商铺信息
		return shop;
	}

	// 获取锁
	private boolean tryLock(String key){
		Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
		return BooleanUtil.isTrue(flag);
	}

	// 释放锁
	private void unLock(String key){
		stringRedisTemplate.delete(key);
	}*/

	// 数据预热
	/*public void saveShopRedis(Long id, Long expireSeconds){
		// 1. 查询店铺数据
		Shop shop = getById(id);
		// 2. 封装逻辑过期时间
		RedisData redisData = new RedisData();
		redisData.setData(shop);
		redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
		// 3. 写入Redis
		stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
	}*/

	/**
	 * 更新商铺信息
	 * @param shop 商铺信息
	 * @return 更新结果
	 */
	@Override
	@Transactional
	public Result updateShop(Shop shop) {
		Long id = shop.getId();
		if(id == null){
			return Result.fail("商铺ID不能为空");
		}
		// 更新数据库
		updateById(shop);
		// 删除缓存
		stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
		return Result.ok();
	}
}
